
#include "EggNinePCH.h"

#include "deferredShaders.h"
#include "deferredTechnique.h"

namespace EggNine {

DefferedTechnique::DefferedTechnique(const string& shaderName)
    : PresentTechnique(shaderName, "DeferredPresent")
{
    char log[5000];
    int effect;
    effect=glfxGenEffect();
    glfxParseEffectFromFile(effect, shaderName.c_str());
    m_gbufferConstructProgram=glfxCompileProgram(effect, "GBuffConstruct");
    glfxGetEffectLog(effect, log, sizeof(log)-1);
#ifdef _DEBUG
    fprintf(stderr, log);
#endif

    m_attribMap["Position"]=REG_POS;
    m_attribMap["Normal"]=REG_NORMAL;
    m_attribMap["TexCoords"]=REG_TEXCOORDS;

    TexUnit tUnit;
    tUnit.m_location=glGetUniformLocation(m_gbufferConstructProgram, "TexDiffuse");
    tUnit.m_unit=0;
    tUnit.m_target=GL_TEXTURE_2D;
    tUnit.m_sampler=glfxGenerateSampler(effect, "MeshTextureSampler");
    m_texUnits["Image"]=tUnit;

    m_lightVolProgram=glfxCompileProgram(effect, "LightVol");
    glfxGetEffectLog(effect, log, sizeof(log)-1);
#ifdef _DEBUG
    fprintf(stderr, log);
#endif

    m_resolveProgram=glfxCompileProgram(effect, "LightResolve");
    glfxGetEffectLog(effect, log, sizeof(log)-1);
#ifdef _DEBUG
    fprintf(stderr, log);
#endif

    // zeroth unit is reserved for input textures (shadow map)
    glProgramUniform1i(m_resolveProgram, glGetUniformLocation(m_resolveProgram, "GBuffDiffuse"), 1);
    glProgramUniform1i(m_resolveProgram, glGetUniformLocation(m_resolveProgram, "GBuffNormal"), 2);
    glProgramUniform1i(m_resolveProgram, glGetUniformLocation(m_resolveProgram, "GBuffDepth"), 3);
    m_GBuffSampler=glfxGenerateSampler(effect, "GBuffSampler");

    glGenFramebuffers(1, &m_gbufferFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, m_gbufferFBO);

    glGenTextures(sizeof(m_gbuffer)/sizeof(GLuint), m_gbuffer);

    m_drawBuffers[0]=GL_COLOR_ATTACHMENT0;
    glFramebufferTexture(GL_FRAMEBUFFER, m_drawBuffers[0], m_gbuffer[0], 0);
    m_drawBuffers[1]=GL_COLOR_ATTACHMENT1;
    glFramebufferTexture(GL_FRAMEBUFFER, m_drawBuffers[1], m_gbuffer[1], 0);
    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, m_gbuffer[2], 0);

    glGenTextures(1, &m_resultTex);
    m_drawBuffers[2]=GL_COLOR_ATTACHMENT2;
    glFramebufferTexture(GL_FRAMEBUFFER, m_drawBuffers[2], m_resultTex, 0);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

DefferedTechnique::~DefferedTechnique()
{
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glDeleteTextures(sizeof(m_gbuffer)/sizeof(GLuint), m_gbuffer);
    glDeleteTextures(1, &m_resultTex);
    glDeleteSamplers(1, &m_GBuffSampler);
    glDeleteFramebuffers(1, &m_gbufferFBO);
    glUseProgram(0);
    glDeleteProgram(m_gbufferConstructProgram);
    glDeleteProgram(m_lightVolProgram);
    glDeleteProgram(m_resolveProgram);
}

void DefferedTechnique::SetClipPlanes(GLfloat nearPlane, GLfloat farPlane)
{
    m_nearPlane=nearPlane;
    m_farPlane=farPlane;
    glProgramUniform2f(m_resolveProgram, glGetUniformLocation(m_resolveProgram, "gNearFar"), nearPlane, farPlane);
}

void DefferedTechnique::SetViewport(GLint x, GLint y, GLsizei width, GLsizei height)
{
    PresentTechnique::SetViewport(x, y, width, height);

    glProgramUniform2f(m_resolveProgram, glGetUniformLocation(m_resolveProgram, "gInverseWidthHeight"), 1.0f/width, 1.0f/height);

    glBindTexture(GL_TEXTURE_RECTANGLE, m_gbuffer[0]);
    glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

    glBindTexture(GL_TEXTURE_RECTANGLE, m_gbuffer[1]);
    glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_HALF_FLOAT, NULL);

    glBindTexture(GL_TEXTURE_RECTANGLE, m_gbuffer[2]);
    //glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_DEPTH24_STENCIL8, width, height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL );
    glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_DEPTH32F_STENCIL8, width, height, 0, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, NULL );

    glBindTexture(GL_TEXTURE_2D, m_resultTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_HALF_FLOAT, NULL);
}

void DefferedTechnique::ResetStateToProgram() const
{
    glBindFramebuffer(GL_FRAMEBUFFER, m_gbufferFBO);
    glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight);

    // No real need to clear the final buffer as it'll be overwritten anyway, but whatever
    glDrawBuffers(sizeof(m_drawBuffers)/sizeof(GLenum), m_drawBuffers);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}

void DefferedTechnique::DrawPass(int pass/* =0 */) const
{
    switch(pass) {
    case GEOMETRY_PASS:
        glUseProgram(m_gbufferConstructProgram);
        
        // The last draw buffer is the result texture
        glDrawBuffers(sizeof(m_drawBuffers)/sizeof(GLenum)-1, m_drawBuffers);

        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LEQUAL);
        glDepthMask(GL_TRUE);
        glDisable(GL_STENCIL_TEST);
        glEnable(GL_CULL_FACE);
        glCullFace(GL_BACK);
        break;

    case FINALIZE_GEOMETRY_PASS:
        glDepthMask(GL_FALSE);
        glDisable(GL_CULL_FACE);
        glEnable(GL_STENCIL_TEST);
        glDrawBuffer(GL_NONE);

        for(int i=0;i<sizeof(m_gbuffer)/sizeof(GLuint);i++) {
            glActiveTexture(GL_TEXTURE1+i);
            glBindTexture(GL_TEXTURE_RECTANGLE, m_gbuffer[i]);
            glBindSampler(i+1, m_GBuffSampler);
        }
        break;

    case LIGHT_VOL_PASS:
        glUseProgram(m_lightVolProgram);

        glDrawBuffer(GL_NONE);

        glDepthFunc(GL_LEQUAL);
        
        glStencilFunc(GL_ALWAYS, 0, -1);
        glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_DECR_WRAP, GL_KEEP);
        glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_INCR_WRAP);
        break;

    case LIGHT_RESOLVE_PASS:
        glUseProgram(m_resolveProgram);

        glDrawBuffer(m_drawBuffers[sizeof(m_drawBuffers)/sizeof(GLuint)-1]);

        glStencilFunc(GL_NOTEQUAL, 0, -1);
        // We'll clear the sbuffer while shading
        glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
        glDepthFunc(GL_ALWAYS);
        break;

    case FINALIZE_PASS:
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glBindTexture(GL_TEXTURE_2D, m_resultTex);
        glGenerateMipmap(GL_TEXTURE_2D);
        break;
    }
}

} // EggNine